package info.niwota.dc.bg;

import info.niwota.dc.Debug;
import info.niwota.dc.R;

import java.util.ArrayList;
import java.util.HashSet;

import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;

/**
 * @author qiangli
 * 
 */
public class MultiSelectImageActivity extends Activity implements
		GridViewSpecial.Listener, GridViewSpecial.DrawAdapter {
	public static final String EXTRA_URIS = "extra.uris"; // pkg name per

	private static final float INVALID_POSITION = -1f;

	private static final String STATE_SCROLL_POSITION = "scroll_position";

	private static final String STATE_SELECTED_INDEX = "first_index";

	private static final String TAG = "MultiSelectImageActivity";

	private RecentImageList mAllImages;

	private boolean mConfigurationChanged = false;
															private final Rect mDstRect = new Rect();
	
	private Animation mFooterAppear;
	
	private Animation mFooterDisappear;

	private View mFooterOrganizeView;

	private GridViewSpecial mGvs;
	
	private final Handler mHandler = new Handler();
	
	private boolean mLayoutComplete;
	
	private ImageLoader mLoader;
	
	// javadoc a must?
	private Dialog mMediaScanningDialog;

	private Bitmap mMissingImageThumbnailBitmap;
	
	private HashSet<IImage> mMultiSelected = null;
	
	private Drawable mMultiSelectFalse;

	private Drawable mMultiSelectTrue;

	private View mNoImagesView;

	private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);

	private boolean mPausing = true;

	private SharedPreferences mPrefs;

	private BroadcastReceiver mReceiver = null;

	private float mScrollPosition = INVALID_POSITION;

	// The index of the first picture in GridViewSpecial.
	private int mSelectedIndex = GridViewSpecial.INDEX_NONE;

	// mSrcRect and mDstRect are only used in drawImage, but we put them as
	// instance variables to reduce the memory allocation overhead because
	// drawImage() is called a lot.
	private final Rect mSrcRect = new Rect();

	boolean canHandleEvent() {
		// Don't process event in pause state.
		return (!mPausing) && (mLayoutComplete);
	}

	private void closeMultiSelectMode() {
		if (mMultiSelected == null) {
			return;
		}
		mMultiSelected = null;
		mGvs.invalidate();
		hideFooter();
	}

	private void doCancel() {
		closeMultiSelectMode();
		//
		setResult(RESULT_CANCELED);
		finish();
	}

	private void doFinish() {
		ArrayList<Uri> list = new ArrayList<Uri>();
		for (IImage image : mMultiSelected) {
			list.add(image.fullSizeImageUri());
		}
		closeMultiSelectMode();

		Intent data = new Intent();
		// data.putExtra(EXTRA_URIS, list);
		data.putParcelableArrayListExtra(EXTRA_URIS, list);
		setResult(RESULT_OK, data);
		finish();
	}

	public void drawDecoration(Canvas canvas, IImage image, int xPos, int yPos,
			int w, int h) {
		if (mMultiSelected != null) {
			initializeMultiSelectDrawables();

			Drawable checkBox = mMultiSelected.contains(image) ? mMultiSelectTrue
					: mMultiSelectFalse;
			int width = checkBox.getIntrinsicWidth();
			int height = checkBox.getIntrinsicHeight();
			int left = 5 + xPos;
			int top = h - height - 5 + yPos;
			mSrcRect.set(left, top, left + width, top + height);
			checkBox.setBounds(mSrcRect);
			checkBox.draw(canvas);
		}
	}

	public void drawImage(Canvas canvas, IImage image, Bitmap b, int xPos,
			int yPos, int w, int h) {
		if (b != null) {
			// if the image is close to the target size then crop,
			// otherwise scale both the bitmap and the view should be
			// square but I suppose that could change in the future.

			int bw = b.getWidth();
			int bh = b.getHeight();

			int deltaW = bw - w;
			int deltaH = bh - h;

			if (deltaW >= 0 && deltaW < 10 && deltaH >= 0 && deltaH < 10) {
				int halfDeltaW = deltaW / 2;
				int halfDeltaH = deltaH / 2;
				mSrcRect.set(0 + halfDeltaW, 0 + halfDeltaH, bw - halfDeltaW,
						bh - halfDeltaH);
				mDstRect.set(xPos, yPos, xPos + w, yPos + h);
				canvas.drawBitmap(b, mSrcRect, mDstRect, null);
			} else {
				mSrcRect.set(0, 0, bw, bh);
				mDstRect.set(xPos, yPos, xPos + w, yPos + h);
				canvas.drawBitmap(b, mSrcRect, mDstRect, mPaint);
			}
		} else {
			// If the thumbnail cannot be drawn, put up an error icon
			// instead
			Bitmap error = getErrorBitmap(image);
			int width = error.getWidth();
			int height = error.getHeight();
			mSrcRect.set(0, 0, width, height);
			int left = (w - width) / 2 + xPos;
			int top = (w - height) / 2 + yPos;
			mDstRect.set(left, top, left + width, top + height);
			canvas.drawBitmap(error, mSrcRect, mDstRect, null);
		}
	}

	public Bitmap getErrorBitmap(IImage image) {
		// Create this bitmap lazily, and only once for all the ImageBlocks to
		// use
		if (mMissingImageThumbnailBitmap == null) {
			mMissingImageThumbnailBitmap = BitmapFactory.decodeResource(
					getResources(), R.drawable.ic_missing_thumbnail_picture);
		}
		return mMissingImageThumbnailBitmap;
	}

	private void hideFooter() {
		if (mFooterOrganizeView.getVisibility() != View.GONE) {
			mFooterOrganizeView.setVisibility(View.GONE);
			if (mFooterDisappear == null) {
				mFooterDisappear = AnimationUtils.loadAnimation(this,
						R.anim.footer_disappear);
			}
			mFooterOrganizeView.startAnimation(mFooterDisappear);
		}
	}

	private void initializeFooterButtons() {
		Button okButton = (Button) findViewById(R.id.button_ok);
		okButton.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				doFinish();
			}
		});

		Button closeButton = (Button) findViewById(R.id.button_close);
		closeButton.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				doCancel();
			}
		});
	}

	private void initializeMultiSelectDrawables() {
		if (mMultiSelectTrue == null) {
			mMultiSelectTrue = getResources().getDrawable(
					R.drawable.btn_check_buttonless_on);
		}
		if (mMultiSelectFalse == null) {
			mMultiSelectFalse = getResources().getDrawable(
					R.drawable.btn_check_buttonless_off);
		}
	}

	private boolean isInMultiSelectMode() {
		return (mMultiSelected != null);
	}

	public boolean needsDecoration() {
		return (mMultiSelected != null);
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		//
	}
	@Override
	public void onConfigurationChanged(Configuration newConfig) {
		super.onConfigurationChanged(newConfig);
		mConfigurationChanged = true;
	}

	public void onCreate(Bundle savedInstanceState) {
		if (Debug.DEBUG) {
			Log.d(TAG, "onCreate savedInstanceState: " + savedInstanceState);
		}
		super.onCreate(savedInstanceState);

		mPrefs = PreferenceManager.getDefaultSharedPreferences(this);

		// Must be called before setContentView().
		requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);

		setContentView(R.layout.image_gallery);

		getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE,
				R.layout.custom_gallery_title);

		mNoImagesView = findViewById(R.id.no_images);

		mGvs = (GridViewSpecial) findViewById(R.id.grid);
		mGvs.setListener(this);

		mFooterOrganizeView = findViewById(R.id.footer_organize);

		// consume all click events on the footer view
		mFooterOrganizeView.setOnClickListener(Util.getNullOnClickListener());

		initializeFooterButtons();

		mLoader = new ImageLoader(getContentResolver(), mHandler);
		//
		openMultiSelectMode();
	}
	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		return false;
	}

	public void onImageClicked(int index) {
		if (index < 0 || index >= mAllImages.getCount()) {
			return;
		}
		mSelectedIndex = index;
		mGvs.setSelectedIndex(index);

		IImage image = mAllImages.getImageAt(index);

		if (isInMultiSelectMode()) {
			toggleMultiSelected(image);
			return;
		}
	}

	public void onImageTapped(int index) {
		// In the multiselect mode, once the finger finishes tapping, we hide
		// the selection box by setting the selected index to none. However, if
		// we use the dpad center key, we will keep the selected index in order
		// to show the the selection box. We do this because we have the
		// multiselect marker on the images to indicate which of them are
		// selected, so we don't need the selection box, but in the dpad case
		// we still need the selection box to show as a "cursor".

		if (isInMultiSelectMode()) {
			mGvs.setSelectedIndex(GridViewSpecial.INDEX_NONE);
			toggleMultiSelected(mAllImages.getImageAt(index));
		} else {
			onImageClicked(index);
		}
	}

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if (!canHandleEvent()) {
			return false;
		}
		if (keyCode == KeyEvent.KEYCODE_BACK) {
			if (mMultiSelected == null || mMultiSelected.size() == 0) {
				doCancel();
				return true;
			}
			return true;
		}
		return super.onKeyDown(keyCode, event);
	}

	public void onLayoutComplete(boolean changed) {
		mLayoutComplete = true;

		mGvs.setSelectedIndex(mSelectedIndex);
		if (mScrollPosition == INVALID_POSITION) {
			mGvs.scrollToImage(0);
		} else if (mConfigurationChanged) {
			mConfigurationChanged = false;
			mGvs.scrollTo(mScrollPosition);
			if (mGvs.getCurrentSelection() != GridViewSpecial.INDEX_NONE) {
				mGvs.scrollToVisible(mSelectedIndex);
			}
		} else {
			mGvs.scrollTo(mScrollPosition);
		}
	}

	@Override
	public void onPause() {
		super.onPause();
		mPausing = true;

		mLoader.stop();

		mGvs.stop();

		if (mReceiver != null) {
			unregisterReceiver(mReceiver);
			mReceiver = null;
		}

		// Now that we've paused the threads that are using the cursor it is
		// safe to close it.
		mAllImages.close();
		mAllImages = null;
	}

	@Override
	public boolean onPrepareOptionsMenu(Menu menu) {
		return false;
	}

	@Override
	protected void onRestoreInstanceState(Bundle state) {
		super.onRestoreInstanceState(state);
		mScrollPosition = state.getFloat(STATE_SCROLL_POSITION,
				INVALID_POSITION);
		mSelectedIndex = state.getInt(STATE_SELECTED_INDEX, 0);
	}

	@Override
	public void onResume() {
		super.onResume();

		mGvs.setSizeChoice(Integer.parseInt(mPrefs.getString(
				"pref_gallery_size_key", "1")));
		mGvs.requestFocus();

		mPausing = false;

		// install an intent filter to receive SD card related events.
		IntentFilter intentFilter = new IntentFilter(
				Intent.ACTION_MEDIA_MOUNTED);
		intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
		intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
		intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
		intentFilter.addAction(Intent.ACTION_MEDIA_EJECT);
		intentFilter.addDataScheme("file");

		mReceiver = new BroadcastReceiver() {
			@Override
			public void onReceive(Context context, Intent intent) {
				String action = intent.getAction();
				if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
					// SD card available
					// TODO put up a "please wait" message
					// TODO also listen for the media scanner finished message
				} else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
					// SD card unavailable
					rebake(true, false);
				} else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
					rebake(false, true);
				} else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
					rebake(false, false);
				} else if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
					rebake(true, false);
				}
			}
		};
		registerReceiver(mReceiver, intentFilter);
		rebake(false, ImageManager.isMediaScannerScanning(getContentResolver()));
	}
	@Override
	protected void onSaveInstanceState(Bundle state) {
		super.onSaveInstanceState(state);
		state.putFloat(STATE_SCROLL_POSITION, mScrollPosition);
		state.putInt(STATE_SELECTED_INDEX, mSelectedIndex);
	}

	public void onScroll(float scrollPosition) {
		mScrollPosition = scrollPosition;
	}

	private void openMultiSelectMode() {
		if (mMultiSelected != null) {
			return;
		}
		mMultiSelected = new HashSet<IImage>();
		mGvs.invalidate();
	}

	private void rebake(boolean unmounted, boolean scanning) {
		if (Debug.DEBUG) {
			Log.d(TAG, "rebake");
		}
		mGvs.stop();
		if (mAllImages != null) {
			mAllImages.close();
			mAllImages = null;
		}

		if (mMediaScanningDialog != null) {
			mMediaScanningDialog.cancel();
			mMediaScanningDialog = null;
		}

		if (scanning) {
			mMediaScanningDialog = ProgressDialog.show(this, null,
					getResources().getString(R.string.wait), true, true);
		}

		//
		mAllImages = new RecentImageList(getContentResolver());
		mAllImages.init();

		mGvs.setImageList(mAllImages);
		mGvs.setDrawAdapter(this);
		mGvs.setLoader(mLoader);
		mGvs.start();
		mNoImagesView.setVisibility(mAllImages.getCount() > 0 ? View.GONE
				: View.VISIBLE);
	}

	private void showFooter() {
		mFooterOrganizeView.setVisibility(View.VISIBLE);
		if (mFooterAppear == null) {
			mFooterAppear = AnimationUtils.loadAnimation(this,
					R.anim.footer_appear);
		}
		mFooterOrganizeView.startAnimation(mFooterAppear);
	}

	private void toggleMultiSelected(IImage image) {
		int original = mMultiSelected.size();
		if (!mMultiSelected.add(image)) {
			mMultiSelected.remove(image);
		}
		mGvs.invalidate();
		if (original == 0) {
			showFooter();
		}
		if (mMultiSelected.size() == 0) {
			hideFooter();
		}
	}
}
